// 
// Entify project
//  
// Author:
//       Lauri Taimila <lauri@taimila.com>
// 
// Copyright (c) 2010 Lauri Taimila
// 
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
// 
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

using System;
using System.Linq;
using System.Reflection;
using System.Collections.Generic;

using Taimila.Entify.Attributes;

namespace Taimila.Entify
{
	/// <summary>
	/// This static class provides helper methods for extracting attribute 
	/// information from types and property infos.
	/// </summary>
	internal static class AttributeReader
	{			
		/// <summary>
		/// Checks if class declaration has [Entity] attribute.
		/// </summary>
		/// <param name="t">
		/// Type to be checked. <see cref="System.Type"/>
		/// </param>
		/// <returns>
		/// True if attribute exists, otherwise false. <see cref="System.Boolean"/>
		/// </returns>
		public static bool HasEntity(Type t)
		{
			if(t == null)
			{
				throw new ArgumentNullException();
			}
			
			if(t.GetCustomAttributes(typeof(EntityAttribute), false).Length == 1)
			{
				return true;
			}
			else
			{
				return false;
			}
		}
		
		/// <summary>
		/// Is given entity type abstract entity type.
		/// </summary>
		/// <param name="t">
		/// Type of the entity. <see cref="Type"/>
		/// </param>
		/// <returns>
		/// True if type is abstract entity type, otherwise false. <see cref="System.Boolean"/>
		/// </returns>
		public static bool IsAbstractEntity(Type t)
		{
			if(t == null)
			{
				throw new ArgumentNullException();
			}
			
			try
			{
				EntityAttribute attribute = (EntityAttribute)t.GetCustomAttributes(typeof(EntityAttribute), false)[0];
				return attribute.Abstract;
			}
			catch(Exception)
			{
				throw new InvalidEntityTypeException(t.Name + " doesn't have [PersistentEntity] attribute.");
			}
		}
		
		/// <summary>
		/// Get entity version number from [PersistentEntity] attribute.
		/// </summary>
		/// <param name="t">
		/// Type that is investigated. <see cref="System.Type"/>
		/// </param>
		/// <returns>
		/// Version number. <see cref="System.Double"/>
		/// </returns>
		public static double GetEntityVersion(Type t)
		{
			if(t == null)
			{
				throw new ArgumentNullException();
			}
			
			try
			{
				EntityAttribute attribute = (EntityAttribute)t.GetCustomAttributes(typeof(EntityAttribute), false)[0];
				return attribute.Version;
			}
			catch(Exception)
			{
				throw new InvalidEntityTypeException(t.Name + " doesn't have [PersistentEntity] attribute.");
			}
		}
		
		/// <summary>
		/// Get all relation types of the given entity type. Relation types are entity
		/// types that are in relation to given type. Each relation type is only once
		/// on the returned list.
		/// </summary>
		/// <param name="type">
		/// Type which relation types are requested.
		/// </param>
		/// <returns>
		/// List of all relation types. <see cref="Type[]"/>
		/// </returns>
		internal static Type[] GetRelationTypes(Type type)
		{
			List<Type> types = new List<Type>();
			
			foreach(var property in type.GetProperties())
			{
				if(AttributeReader.HasEntityProperty(property) && AttributeReader.IsRelationship(property))
				{
					if(property.PropertyType.IsGenericType && !types.Contains(property.PropertyType))
					{
						types.Add(property.PropertyType.GetGenericArguments().Single());
					}
					else if(!types.Contains(property.PropertyType))
					{
						types.Add(property.PropertyType);
					}
				}
			}
			
			return types.ToArray();
		}
		
		/// <summary>
		/// Checks if property has any kind of validator attribute.
		/// </summary>
		/// <param name="propertyInfo">
		/// Property to be checked. <see cref="PropertyInfo"/>
		/// </param>
		/// <returns>
		/// True if validator attribute exists, otherwise false. <see cref="System.Boolean"/>
		/// </returns>
		public static bool HasValidator(PropertyInfo propertyInfo)
		{
			if(propertyInfo == null)
			{
				throw new ArgumentNullException();
			}
			
			if(propertyInfo.GetCustomAttributes(typeof(ValidatorAttribute), false).Length > 0)
			{
				return true;	
			}
			else
			{
				return false;
			}
		}
		
		/// <summary>
		/// Checks if property has [EntityProperty] attribute.
		/// </summary>
		/// <param name="propertyInfo">
		/// Property to be checked. <see cref="PropertyInfo"/>
		/// </param>
		/// <returns>
		/// True if attribute exists, otherwise false. <see cref="System.Boolean"/>
		/// </returns>
		public static bool HasEntityProperty(PropertyInfo propertyInfo)
		{
			if(propertyInfo == null)
			{
				throw new ArgumentNullException();
			}
			
			if(propertyInfo.GetCustomAttributes(typeof(EntityPropertyAttribute), false).Length == 1)
			{
				return true;	
			}
			else
			{
				return false;
			}
		}
		
		/// <summary>
		/// Checks does given proprety have persistent name.
		/// </summary>
		/// <param name="propertyInfo">
		/// Property to be checked <see cref="PropertyInfo"/>
		/// </param>
		/// <returns>
		/// True if property has persistent name set, otherwise false. <see cref="System.Boolean"/>
		/// </returns>
		public static bool HasPersistentName(PropertyInfo propertyInfo)
		{
			try
			{
				EntityPropertyAttribute attribute = (propertyInfo.GetCustomAttributes(typeof(EntityPropertyAttribute), false)[0]) as EntityPropertyAttribute;
				if(attribute.PersistentName.Length > 0)
				{
					return true;
				}
				else
				{
					return false;
				}
			}
			catch(NullReferenceException)
			{
				return false;	
			}
		}
		
		/// <summary>
		/// Checks is property marked as an unique field.
		/// </summary>
		/// <param name="propertyInfo">
		/// Property to be checked. <see cref="PropertyInfo"/>
		/// </param>
		/// <returns>
		/// True if unique, otherwise false. <see cref="System.Boolean"/>
		/// </returns>
		public static bool IsUnique(PropertyInfo propertyInfo)
		{
			if(propertyInfo == null)
			{
				throw new ArgumentNullException();
			}
			
			try
			{
				EntityPropertyAttribute attribute = (EntityPropertyAttribute) propertyInfo.GetCustomAttributes(typeof(EntityPropertyAttribute), false)[0];
				return attribute.Unique;
			}
			catch(NullReferenceException)
			{
				throw new ArgumentException(propertyInfo.Name + " doesn't have [PersistentData] attribute.");
			}
		}
		
		/// <summary>
		/// Checks is property many-to-many relationship.
		/// </summary>
		/// <param name="propertyInfo">
		/// Property to be checked. <see cref="PropertyInfo"/>
		/// </param>
		/// <returns>
		/// True if many-to-many relationship, otherwise false. <see cref="System.Boolean"/>
		/// </returns>
		public static bool IsManyRelationship(PropertyInfo propertyInfo)
		{
			if(propertyInfo == null)
			{
				throw new ArgumentNullException();
			}
			
			try
			{
				EntityPropertyAttribute attribute = (EntityPropertyAttribute) propertyInfo.GetCustomAttributes(typeof(EntityPropertyAttribute), false)[0];
				if(attribute.Relationship && propertyInfo.PropertyType.IsGenericType)
				{
					return true;
				}
				else
				{
					return false;	
				}
			}
			catch(IndexOutOfRangeException)
			{
				return false;
			}
		}
		
		/// <summary>
		/// Checks is property marked as relationship.
		/// </summary>
		/// <param name="propertyInfo">
		/// Property to be checked. <see cref="PropertyInfo"/>
		/// </param>
		/// <returns>
		/// True if relationship, otherwise false. <see cref="System.Boolean"/>
		/// </returns>
		public static bool IsRelationship(PropertyInfo propertyInfo)
		{
			if(propertyInfo == null)
			{
				throw new ArgumentNullException();
			}
			
			try
			{
				EntityPropertyAttribute attribute = (propertyInfo.GetCustomAttributes(typeof(EntityPropertyAttribute), false)[0]) as EntityPropertyAttribute;
				return attribute.Relationship;
			}
			catch(NullReferenceException)
			{
				throw new ArgumentException(propertyInfo.Name + " doesn't have [PersistentData] attribute.");
			}
		}

		/// <summary>
		/// Get relation type of the relation property.
		/// </summary>
		/// <param name="propertyInfo">
		/// Property which relation type is returned. <see cref="PropertyInfo"/>
		/// </param>
		/// <returns>
		/// Type of the relation. <see cref="Type"/>
		/// </returns>
		public static Type GetRelationType(PropertyInfo propertyInfo)
		{
			if(propertyInfo == null)
			{
				throw new ArgumentNullException();
			}

			if(!AttributeReader.IsRelationship(propertyInfo))
			{
				throw new ArgumentException("Cannot resolve relation type. Property '" + propertyInfo.Name + "' is not relation property.");
			}

			if(AttributeReader.IsManyRelationship(propertyInfo))
			{
				return propertyInfo.PropertyType.GetGenericArguments().Single();
			}
			else
			{
				return propertyInfo.PropertyType;
			}
		}
		
		/// <summary>
		/// Get persistent name of the property.
		/// </summary>
		/// <param name="propertyInfo">
		/// Property. <see cref="PropertyInfo"/>
		/// </param>
		/// <returns>
		/// Persistent name. <see cref="System.Boolean"/>
		/// </returns>
		public static string GetPersistentName(PropertyInfo propertyInfo)
		{
			try
			{
				EntityPropertyAttribute attribute = (propertyInfo.GetCustomAttributes(typeof(EntityPropertyAttribute), false)[0]) as EntityPropertyAttribute;
				return attribute.PersistentName;
			}
			catch(NullReferenceException)
			{
				throw new ArgumentException("Proprety is not marked with Persistent Data attribute.");
			}
		}
		
		/// <summary>
		/// Get index name of the property.
		/// </summary>
		/// <param name="propertyInfo">
		/// Property. <see cref="PropertyInfo"/>
		/// </param>
		/// <returns>
		/// Index name or null if property doesn't have index name set. <see cref="System.Boolean"/>
		/// </returns>
		public static string GetIndexName(PropertyInfo propertyInfo)
		{			
			try
			{
				EntityPropertyAttribute attribute = (propertyInfo.GetCustomAttributes(typeof(EntityPropertyAttribute), false)[0]) as EntityPropertyAttribute;
				
				if(attribute.IndexName == null || attribute.IndexName.Length == 0)
				{
					return null;	
				}
				else
				{
					return attribute.IndexName;
				}
			}
			catch(NullReferenceException)
			{
				throw new ArgumentException("Proprety is not marked with Persistent Data attribute.");
			}
		}
		
		/// <summary>
		/// Check is given method marked with EntityValidator attribute.
		/// </summary>
		/// <param name="methodInfo">
		/// Method. <see cref="MethodInfo"/>
		/// </param>
		/// <returns>
		/// True if method is entity validator, otherwise false. <see cref="System.Boolean"/>
		/// </returns>
		public static bool IsValidatorMethod(MethodInfo methodInfo)
		{
			var attributes = methodInfo.GetCustomAttributes(typeof(EntityValidatorAttribute), false);
			if(attributes.Length > 0)
			{
				return true;	
			}
			else
			{
				return false;
			}
		}
	}
}
